package com.dog.gateway.filter;

import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson2.JSON;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.dog.framework.core.constant.SecurityConstants;
import com.dog.framework.core.enums.ResponseCodeEnum;
import com.dog.framework.core.model.JwtUser;
import com.dog.framework.core.model.ResponseResult;
import com.dog.framework.core.utils.JsonUtils;
import com.dog.framework.core.utils.JwtUtils;
import com.dog.framework.core.utils.ServletUtils;
import com.dog.framework.redis.service.RedisService;
import com.dog.gateway.config.properties.IgnoreWhiteProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

//import org.springframework.data.redis.core.StringRedisTemplate;

/**
 * @author 公众号：码猿技术专栏
 * 全局过滤器，对token的拦截，解析token放入header中，便于下游微服务获取用户信息
 * 分为如下几步：
 *  1、白名单直接放行
 *  2、校验token
 *  3、读取token中存放的用户信息
 *  4、重新封装用户信息，加密成功json数据放入请求头中传递给下游微服务
 */
@Slf4j
@Component
public class GlobalAuthenticationFilter implements GlobalFilter, Ordered {

    /**
     * 排除过滤的 uri 地址，nacos自行添加
     */
    @Autowired
    private IgnoreWhiteProperties whiteUrls;

    @Autowired
    private RedisService redisService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();
        ServerHttpRequest.Builder mutate = exchange.getRequest().mutate();
        //1、白名单放行， 跳过不需要验证的路径
        if (checkUrls(whiteUrls.getWhites(), requestUrl)) {
            //继续路由
            return chain.filter(exchange);
        }

        //2、 检查token是否存在
        String token = getToken(exchange);
        if (StringUtils.isBlank(token)) {
            return invalidTokenMono(exchange);
        }

        try {
            //3. 验证是否过期
            if (JwtUtils.isExpiration(token)) {
                return invalidTokenMono(exchange);
            }

            //4. 是否黑名单中
            // 令牌的唯一ID
            String jti = JwtUtils.getJwtJti(token);
            //查看黑名单中是否存在这个jti，如果存在则这个令牌不能用
            if (redisService.hasKey(SecurityConstants.JWT_JTI_KEY_PREFIX + jti)) {
                return invalidTokenMono(exchange);
            }

            //5. 判断当前用户请求的URL是否有权限
            List<String> authorities = getAuthorities(exchange);
            List<String> roleCodeList = JwtUtils.getRoleCodeList(token);
            if (!hasPermissions(authorities, roleCodeList)) {
                log.info("用户没有请求权限！");
                return invalidTokenMono(exchange);
            }

            //6.解析token
            JwtUser jwtUser = JwtUtils.decode(token);
            //7.将解析后的token加密放入请求头中，方便下游微服务解析获取用户信息
            String base64 = Base64.encode(JsonUtils.toJsonString(jwtUser));
            //8.放入请求头中
            addHeader(mutate, SecurityConstants.JWT_USER, base64);
            //9 内部请求来源参数清除
            removeHeader(mutate, SecurityConstants.FROM);
            //10.继续路由
            return chain.filter(exchange.mutate().request(mutate.build()).build());
        } catch (JWTVerificationException e) {
            //解析token异常，直接返回token无效
            return invalidTokenMono(exchange);
        }
    }


    @Override
    public int getOrder() {
        return 1;
    }

    /**
     * 对url进行校验匹配
     */
    private boolean checkUrls(List<String> urls, String path) {
        if (StringUtils.isEmpty(path) || CollectionUtils.isEmpty(urls)) {
            return false;
        }
        AntPathMatcher pathMatcher = new AntPathMatcher();
        for (String url : urls) {
            if (pathMatcher.match(url, path)) {
                return true;
            }
        }
        return false;
    }


    private List<String> getAuthorities(ServerWebExchange exchange) {
        //匹配url
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        //从Redis中获取当前路径可访问角色列表
        URI uri = exchange.getRequest().getURI();
        //请求方法 POST,GET
        String method = exchange.getRequest().getMethodValue();
        /**
         * TODO 为了适配restful接口，比如 GET:/api/.... POST:/api/....  *:/api/.....  星号匹配所有
         */
        String restFulPath = method + SecurityConstants.METHOD_SUFFIX + uri.getPath();

        //获取所有的uri->角色对应关系
        Map<String, List<String>> entries = redisService.getCacheMap(SecurityConstants.RESOURCE_ROLES_MAP_KEY);
        //角色集合
        List<String> authorities = new ArrayList<>();
        entries.forEach((path, roles) -> {
            //路径匹配则添加到角色集合中
            if (antPathMatcher.match(path, restFulPath)) {
                authorities.addAll(roles);
            }
        });
        return authorities;
    }


    private boolean hasPermissions(Collection<String> authorities, List<String> roleCodes) {
        if (CollectionUtils.isEmpty(authorities)) {
            return Boolean.TRUE;
        }
        if (CollectionUtils.isEmpty(roleCodes)) {
            return Boolean.FALSE;
        }
        List<String> intersectionList =
                roleCodes.stream().filter((firstItem) -> authorities.contains(firstItem)).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(intersectionList)) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }


    /**
     * 从请求头中获取Token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst(SecurityConstants.TOKEN_AUTHENTICATION);
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.replace(SecurityConstants.TOKEN_PREFIX, "");
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }

    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = ServletUtils.urlEncode(valueStr);
        mutate.header(name, valueEncode);
    }

    private void removeHeader(ServerHttpRequest.Builder mutate, String name) {
        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
    }


    /**
     * 无效的token
     */
    private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
        return buildReturnMono(ResponseResult.fail(ResponseCodeEnum.INVALID_TOKEN.getCode(),
                ResponseCodeEnum.INVALID_TOKEN.getMsg()), exchange);
    }


    private Mono<Void> buildReturnMono(ResponseResult responseResult, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset:utf-8");
        DataBuffer buffer =
                response.bufferFactory().wrap(JSON.toJSONString(responseResult).getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
}
